package com.gframework.mybatis.util.generator.core.api;

import java.io.File;
import java.nio.file.Files;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

import com.gframework.mybatis.util.generator.core.codegen.DaoXmlGenerator;
import com.gframework.mybatis.util.generator.core.codegen.JavaBeanGenerator;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.Interface;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.TopLevelClass;
import com.gframework.mybatis.util.generator.core.codegen.dom.xml.Document;
import com.gframework.mybatis.util.generator.core.conf.Configuration;
import com.gframework.mybatis.util.generator.core.conf.Context;
import com.gframework.mybatis.util.generator.core.conf.MetaData;
import com.gframework.mybatis.util.generator.core.exception.GeneratorException;

/**
 * 逆向工程生成mybatis实体类，映射xml，dao接口.<br>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @see Configuration 配置参数封装类 Configuration
 * @see MetaData 数据表信息描述接口 MetaData
 */
public class GeneratorMybatis {

	/** 配置信息. */
	private Configuration config;

	public GeneratorMybatis(Configuration config) {
		this.config = config;
	}

	/**
	 * 调用此方法即可完成所有的生成操作.<br>
	 */
	public void generator() {
		Context context = new Context(config);
		Map<String, List<MetaData>> tables;
		
		try {
			tables = context.analysisTables();
			int count = tables.values().stream().flatMapToInt((t) -> IntStream.of(t.size())).sum();
			System.out.println("表解析完毕，一共解析了 " + count + " 张表。");
		} catch (SQLException e) {
			throw new GeneratorException(e);
		}

		DaoXmlGenerator dx = new DaoXmlGenerator(config);
		JavaBeanGenerator jb = new JavaBeanGenerator(config);

		for (Map.Entry<String, List<MetaData>> entry : tables.entrySet()) {

			String moduleName = entry.getKey();
			List<MetaData> tableInfo = entry.getValue();

			List<TopLevelClass> beans = new ArrayList<>();
			List<Interface> interfaces = new ArrayList<>();
			List<Document> xmls = new ArrayList<>();

			for (MetaData table : tableInfo) {

				TopLevelClass cls = jb.createJavaBean(table);
				Interface inter = dx.createDao(table);
				Document xml = dx.createXml(table);
				if (cls != null) {
					beans.add(cls);
				}
				if (inter != null) {
					interfaces.add(inter);
				}
				if (xml != null) {
					xmls.add(xml);
				}
			}

			this.writerCode(moduleName, beans, interfaces, xmls);

		}

	}

	/**
	 * 将代码写入文件.
	 * 
	 * @param moduleName 模块名称
	 * @param beans 实体类集合
	 * @param interfaces dao接口集合
	 * @param xmls xml配置文件集合
	 */
	private void writerCode(String moduleName, List<TopLevelClass> beans, List<Interface> interfaces,
			List<Document> xmls) {
		File src = null;
		if (this.config.getBaseSrc() == null) {
			src = this.searchSrcPath();
		} else {
			src = new File(this.config.getBaseSrc());
		}
		if (src == null) {
			throw new GeneratorException("无法确定文件生成路径！");
		}
		try {
			if (!src.exists()) {
				Files.createDirectories(src.toPath());
			}
			String basePath = src.getPath() + File.separator + this.config.getBasePackage().replace(".", File.separator)
					+ File.separator + moduleName;
			File beanPath = new File(basePath + (File.separator + "entity" + File.separator + "pojo"));
			File daoPath = new File(basePath + (File.separator + "dao"));
			if (!beanPath.exists()) {
				Files.createDirectories(beanPath.toPath());
			}
			if (!daoPath.exists()) {
				Files.createDirectories(daoPath.toPath());
			}

			for (TopLevelClass b : beans) {
				File f = new File(beanPath.getPath() + File.separator + b.getType().getShortName() + ".java");
				if (f.exists()) {
					if (this.config.isWriterableBean()) {
						Files.write(f.toPath(), b.getFormattedContent().getBytes());
						System.out.println("生成了一个javaBean：" + f.getPath());
					} else {
						System.out.println("javabean已存在：" + f.getPath());
					}
				} else {
					Files.write(f.toPath(), b.getFormattedContent().getBytes());
					System.out.println("生成了一个javaBean：" + f.getPath());
				}
			}
			for (Interface b : interfaces) {
				File f = new File(daoPath.getPath() + File.separator + b.getType().getShortName() + ".java");
				if (f.exists()) {
					if (this.config.isWriterableDao()) {
						Files.write(f.toPath(), b.getFormattedContent().getBytes());
						System.out.println("生成了一个javaDao接口：" + f.getPath());
					} else {
						System.out.println("DAO接口已存在：" + f.getPath());
					}
				} else {
					Files.write(f.toPath(), b.getFormattedContent().getBytes());
					System.out.println("生成了一个javaDao接口：" + f.getPath());
				}
			}
			if (!this.config.isNoXml()) {
				for (Document b : xmls) {
					File f = new File(daoPath.getPath() + File.separator + b.getFileName());
					if (f.exists()) {
						if (this.config.isWriterableXml()) {
							Files.write(f.toPath(), b.getFormattedContent().getBytes());
							System.out.println("生成了一个mybatisXml映射文件：" + f.getPath());
						} else {
							System.out.println("XML文件以存在：" + f.getPath());
						}
					} else {
						Files.write(f.toPath(), b.getFormattedContent().getBytes());
						System.out.println("生成了一个mybatisXml映射文件：" + f.getPath());
					}
				}
			}
		} catch (Exception e) {
			throw new GeneratorException(e);
		}

	}

	/**
	 * 寻找src可能所在的目录.<br>
	 * 如果没有设置生成目录，则会尝试寻找源代码所在目录，由于此路径和class存放路径是无任何关联的，因此
	 * 可能无法准确的找到，如果没有找到，则会抛出异常。默认会按照maven标准的目录结构进行查找。
	 */
	private File searchSrcPath() {
		try {
			File f = new File(ClassLoader.getSystemClassLoader().getResource(".").toURI());
			// 寻找10级，在往上就不找了
			int findLevel = 10;
			String[] findDir = new String[] { "src", "main", "java" };
			File lastFindFile = null;
			for (int x = 0; x < findLevel; x ++) {
				f = f.getParentFile();
				lastFindFile = searchPath(f, 0, findDir, lastFindFile);
				if (lastFindFile != null) {
					return lastFindFile;
				}
			}
			return null;
		} catch (Exception e) {
			throw new GeneratorException(e);
		}
	}

	/**
	 * 本方法负责递归的寻找某个目录下的指定目录
	 * 
	 * @param file 当前文件
	 * @param index 要查询的目录名称所在findDir数组的索引
	 * @param findDir 要查询的目录
	 * @param lastFindDir 上一个进行查询的目录，已经查询过一次不在做重复查找。
	 * @return 如果找到则返回，否则返回null
	 */
	private File searchPath(File file, int index, String[] findDir, File lastFindDir) {
		for (File f : file.listFiles()) {
			if (lastFindDir != null && f.getPath().equals(lastFindDir.getPath())) {
				continue;
			}
			if (f.isDirectory()) {
				File of = null;
				if (f.getName().equals(findDir[index])) {
					if (index == findDir.length - 1) {
						return f;
					}
					of = searchPath(f, index + 1, findDir, lastFindDir);
				} else {
					of = searchPath(f, index, findDir, lastFindDir);
				}
				if (of != null) {
					return of;
				}
			}
		}
		return null;
	}

}
